#include <algorithm>
#include <cmath>
#include <GL/glut.h>
#include <iostream>

#include "Visualization.h"
#include "Simulation.h"
#include "Utilities.h"

using namespace std;

Visualization::Visualization()
:
    d_legend_scalar(3.0, 8.0, 1.5, 1.5, &d_map_scalar, "scalars"),
    d_legend_vector(3.0, 8.0, 1.5, 10.0, &d_map_vector, "vector")
{
    vec_scale = 1000;
    scalar_col = Rainbow;
    colormodes = {"Black and White", "Rainbow", "Ocean", "Custom Hue"};
    datasets = {"Fluid Density", "Fluid Velocity", "Force Magnitude"};
    vectordatasets = {"Fluid Velocity", "Force Field"};
    glyphtypes = {"Hedgehogs"};
    options[UseDirectionColoring] = false;
    options[DrawSmoke] = false;
    options[DrawVectorField] = true;	
    options[UseLogScaling] = false;
}

void Visualization::toggle(Option option)
{
	options[option] = !options[option];
}

void Visualization::enable(Option option)
{
	options[option] = true;
}

void Visualization::disable(Option option)
{
	options[option] = false;
}

bool Visualization::is_enabled(Option option) const
{
	return options[option];
}

void Visualization::scale_hedgehogs(float scale)
{
	vec_scale *= scale;
}

void Visualization::set_hedgehog_scale(float scale)
{
    vec_scale = scale;
}

float Visualization::hedgehog_scale() const
{
	return vec_scale;
}

////rainbow: Implements a color palette, mapping the scalar 'value' to a rainbow color RGB
//void Visualization::rainbow(float value, float* R, float* G, float* B)
//{
//	const float dx=0.8;	
//        value = (value < 0) ? 0 : (value > 1) ? 1 : value; // crop value to [0,1];
//	value = (6 - 2 * dx) * value + dx;                 // scale value to [dx, 6 - dx]
//	*R = max(0.0, (3-fabs(value-4)-fabs(value-5)) / 2.0);
//	*G = max(0.0, (4-fabs(value-2)-fabs(value-4)) / 2.0);
//	*B = max(0.0, (3-fabs(value-1)-fabs(value-2)) / 2.0);
//}

//set_colormap: Sets three different types of colormaps
void Visualization::set_colormap(float vy)
{
    d_map_scalar.set(vy);
}

float Visualization::get_vector_x(Simulation const &simulation, int idx) 
{
    float ret;
    if (d_vector_dataset == ForceField)
    {
        ret = simulation.getVectorX(Simulation::ForceField, idx);
    }
    else 
    {
        ret = simulation.getVectorX(Simulation::FluidVelocity, idx);
    }
    return ret;
}

float Visualization::get_vector_y(Simulation const &simulation, int idx) 
{
    float ret;
    if (d_vector_dataset == ForceField)
    {
        ret = simulation.getVectorY(Simulation::ForceField, idx);
    }
    else 
    {
        ret = simulation.getVectorY(Simulation::FluidVelocity, idx);
    }
    return ret;   
}


// Returns data point value according to current visualized data set
 float Visualization::get_datapoint(Simulation const &simulation, int idx)
 {
     if (d_dataset == Velocity) 
     {
         return simulation.getDataPoint(Simulation::Velocity, idx);
     }
     else if (d_dataset == Force) 
     {
         return simulation.getDataPoint(Simulation::Force, idx);
     }
     else
     {
         d_dataset = Density;
         return simulation.getDataPoint(Simulation::Density, idx);
     }
 }
 
 //Returns current dataset maximum
    float Visualization::getMax(Simulation const &simulation)
    {
        if (d_dataset == Velocity)
        {
            return simulation.getMax(Simulation::Velocity);
        }
        else if (d_dataset == Force)
        {
            return simulation.getMax(Simulation::Force);
        }
        else
        {
            return simulation.getMax(Simulation::Density);
        }
    }

 float Visualization::getMin(Simulation const &simulation)
 {
     if (d_dataset == Velocity)
     {
         return simulation.getMin(Simulation::Velocity);
     }
     else if (d_dataset == Force)
     {
        return simulation.getMin(Simulation::Force);
     }
     else
     {
         return simulation.getMin(Simulation::Density);
     }
 }
 
//direction_to_color: Set the current color by mapping a direction vector (x,y), using
//                    the color mapping method 'method'. If method==1, map the vector direction
//                    using a rainbow colormap. If method==0, simply use the white color
void Visualization::direction_to_color(float x, float y, int method)
{
	float r,g,b,f;
	if (method)
	{
	  f = atan2(y,x) / 3.1415927 + 1;
	  r = f;
	  if(r > 1) r = 2 - r;
	  g = f + .66667;
      if(g > 2) g -= 2;
	  if(g > 1) g = 2 - g;
	  b = f + 2 * .66667;
	  if(b > 2) b -= 2;
	  if(b > 1) b = 2 - b;
	}
	else
	{ r = g = b = 1; }
	glColor3f(r,g,b);
}

		
//visualize: This is the main visualization function (scalar)
void Visualization::visualize(Simulation const &simulation, int winWidth, int winHeight)
{
    int        i, j, idx; double px,py;
    const int DIM = Simulation::DIM;
    fftw_real  wn = (fftw_real)winWidth / (fftw_real)(DIM + 1);   // Grid cell width
    fftw_real  hn = (fftw_real)winHeight / (fftw_real)(DIM + 1);  // Grid cell heigh

    d_map_scalar.minimum(getMin(simulation));
    d_map_scalar.maximum(getMax(simulation));

    if (options[DrawSmoke])
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        for (j = 0; j < DIM - 1; j++)			//draw smoke
        {
        glBegin(GL_TRIANGLE_STRIP);

            i = 0;
            px = wn + (fftw_real)i * wn;
            py = hn + (fftw_real)j * hn;
            idx = (j * DIM) + i;
            d_map_scalar.set(get_datapoint(simulation, idx));
            glVertex2f(px, py);

            for (i = 0; i < DIM - 1; i++)
            {
                px = wn + (fftw_real)i * wn;
                py = hn + (fftw_real)(j + 1) * hn;
                idx = ((j + 1) * DIM) + i;
                d_map_scalar.set(get_datapoint(simulation, idx));
                glVertex2f(px, py);
                px = wn + (fftw_real)(i + 1) * wn;
                py = hn + (fftw_real)j * hn;
                idx = (j * DIM) + (i + 1);
                d_map_scalar.set(get_datapoint(simulation, idx));
                glVertex2f(px, py);
            }

            px = wn + (fftw_real)(DIM - 1) * wn;
            py = hn + (fftw_real)(j + 1) * hn;
            idx = ((j + 1) * DIM) + (DIM - 1);
            d_map_scalar.set(get_datapoint(simulation, idx));
            glVertex2f(px, py);
        glEnd();
        }
        
    }



    if (options[DrawVectorField])
    {
        float vx,vy,gx,gy;
        for (i = 0; i < DIM; i++)
        {
            for (j = 0; j < DIM; j++)
            {
               idx = (j * DIM) + i;
               gx = wn + (fftw_real)i * wn;
               gy = hn + (fftw_real)j * hn;
               vx = get_vector_x(simulation, idx);
               vy = get_vector_y(simulation, idx);
               drawGlyph(vx, vy, gx, gy);
            }
        }
        d_legend_vector.setUnits(wn, hn);
        d_legend_vector.draw();
    } 
    
    d_legend_scalar.setUnits(wn, hn);    
    d_legend_scalar.draw();
    
}

void Visualization::drawGlyph(float vx,float vy, float gx, float gy) {
    if (d_glyphtype == Hedgehog) {
        glBegin(GL_LINES);
        //direction_to_color(simulation.vx[idx], simulation.vy[idx], options[UseDirectionColoring]);
        glVertex2f(gx,gy);
        glVertex2f(gx+vec_scale*vx, gy+vec_scale*vy);
        glEnd();
    }
}


void Visualization::setColorMode(int colorMode)
{
    d_map_scalar.colorMode(colorMode);
}


void Visualization::setDataSet(int dataSet) 
{
    d_dataset = (Dataset) dataSet;
}

void Visualization::setVectorDataSet(int vectorDataSet)
{
    d_vector_dataset = (VectorDataSet) vectorDataSet;
}

void Visualization::setScaleMode(int sm)
{
    d_map_scalar.scaleMode(sm);
}

void Visualization::setColorModeVec(int colorMode)
{
    d_map_vector.colorMode(colorMode);
}

void Visualization::setScaleModeVec(int sm)
{
    d_map_vector.scaleMode(sm);
}

fftw_real Visualization::getInterDataPoint(Simulation const &sim, Simulation::Dataset ds, float x, float y)
{
    int dim = sim.DIM;
    /*
     12
     34
    */
    
    int idx1 = floor(y) * dim + floor(x);
    int idx2 = floor(y) * dim + floor(x + 1);
    int idx3 = floor(y + 1) * dim + floor(x);
    int idx4 = floor(y + 1) * dim + floor(x + 1);
    
    fftw_real f1 = sim.getDataPoint(ds, idx1);
    fftw_real f2 = sim.getDataPoint(ds, idx2);
    fftw_real f3 = sim.getDataPoint(ds, idx3);
    fftw_real f4 = sim.getDataPoint(ds, idx4);
    
    float dum;
    float r = 1 - modf(x, &dum);
    float s = 1 - modf(y, &dum);
    
    float leftMean = s * f1 + (s-1) * f3;
    float rightMean = s * f2 + (s-1) * f4;
    
    return r * leftMean + (r-1) * rightMean;
}


